let findLastEvent;
["click", "touchstart", "mousedown", "keydown", "mouseover"].forEach(
  (eventType) => {
    document.addEventListener(
      eventType,
      (event) => {
        findLastEvent = event;
      },
      {
        capture: true, // 是在捕获阶段还是冒泡阶段执行
        passive: true, // 默认不阻止默认事件
      }
    );
  }
);
function getLastEvent () {
  return findLastEvent
}
function selectors(path) {
  // 反转 + 过滤 + 映射 + 拼接
  return path
    .reverse()
    .filter((element) => {
      return element !== document && element !== window;
    })
    .map((element) => {
      console.log("element", element.nodeName);
      let selector = "";
      if (element.id) {
        return `${element.nodeName.toLowerCase()}#${element.id}`;
      } else if (element.className && typeof element.className === "string") {
        return `${element.nodeName.toLowerCase()}.${element.className}`;
      } else {
        selector = element.nodeName.toLowerCase();
      }
      return selector;
    })
    .join(" ");
}
function getSelector (pathsOrTarget) {
  if (Array.isArray(pathsOrTarget)) {
    return selectors(pathsOrTarget);
  } else {
    let path = [];
    while (pathsOrTarget) {
      path.push(pathsOrTarget);
      pathsOrTarget = pathsOrTarget.parentNode;
    }
    return selectors(path);
  }
}
function getLines(stack) {
  if (!stack) return 
  return stack
    .split("\n")
    .slice(1)
    .map((item) => item.replace(/^\s+at\s+/g, ""))
    .join("^");
}

https://blog.csdn.net/xgangzai/article/details/123814403?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-123814403-blog-112863358.235%5Ev43%5Epc_blog_bottom_relevance_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-123814403-blog-112863358.235%5Ev43%5Epc_blog_bottom_relevance_base3&utm_relevant_index=2

window.$ajax = function (params) {
  const body = Object.assign(
    {
      title: document.title,
      url: location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent
    },
    params
  )
  var init = {
    method: 'POST',
    headers: {//定制http请求的标头
      "Content-Type": "application/json"
    },
    body: JSON.stringify(body)
  }
  console.log('上传日志', body);
  // fetch('http://localhost:8092/errorReport/errorUpload', init)
}

/**
 * jsError
 {
  "title": "前端监控系统", // 页面标题
  "url": "http://localhost:8080/", // 页面URL
  "timestamp": "1590815288710", // 访问时间戳
  "userAgent": "Chrome", // 用户浏览器类型
  "kind": "stability", // 大类
  "type": "error", // 小类
  "errorType": "jsError", // 错误类型
  "message": "Uncaught TypeError: Cannot set property 'error' of undefined", // 类型详情
  "filename": "http://localhost:8080/", // 访问的文件名
  "position": "0:0", // 行列信息
  "stack": "btnClick (http://localhost:8080/:20:39)^HTMLInputElement.onclick (http://localhost:8080/:14:72)", // 堆栈信息
  "selector": "HTML BODY #container .content INPUT" // 选择器
 }
 
 * promiseError
  "errorType": "promiseError",//错误类型 
  "message": "someVar is not defined",//类型详情
  "stack": "http://localhost:8080/:24:29^new Promise (<anonymous>)^btnPromiseClick (http://localhost:8080/:23:13)^HTMLInputElement.onclick (http://localhost:8080/:15:86)",//堆栈信息 
  "selector": "HTML BODY #container .content INPUT"//选择器

 * resourceError
  "errorType": "resourceError",//错误类型
  "filename": "http://localhost:8080/error.js",//访问的文件名
  "tagName": "SCRIPT",//标签名
  "timeStamp": "76",//时间
 */


window.debugger = function (KEY) {
  // 全局错误处理
  window.onerror = function (event) {}

  // 资源加载错误
  /**
   * <img src="xxx.png" />
     <script src="/debugge121r.js"></script>
   */
  window.addEventListener('error', function(event) {
    console.log(event);
    let lastEvent = getLastEvent();
    // 有 e.target.src(href) 的认定为资源加载错误
    if (event.target && (event.target.src || event.target.href)) {
      window.$ajax({
        KEY: KEY,
        //资源加载错误
        kind: "stability", //稳定性指标
        type: "error", //resource
        errorType: "resourceError",
        filename: event.target.src || event.target.href, //加载失败的资源
        tagName: event.target.tagName, //标签名
        timeStamp: event.timeStamp, //时间
        selector: getSelector(event.path || event.target), //选择器
      });
    } else {
      window.$ajax({
        KEY: KEY,
        kind: "stability", //稳定性指标
        type: "error", //error
        errorType: "jsError", //jsError
        message: event.message, //报错信息
        filename: event.filename, //报错链接
        position: (event.lineNo || 0) + ":" + (event.columnNo || 0), //行列号
        stack: getLines(event.error?.stack), //错误堆栈
        selector: lastEvent
          ? getSelector(lastEvent.path || lastEvent.target)
          : "", //CSS选择器
      });
    }

  }, true)

  // promise加载错误
  window.addEventListener('unhandledrejection', function(event) {
    let lastEvent = getLastEvent();
    let message = "";
    let line = 0;
    let column = 0;
    let file = "";
    let stack = "";
    if (typeof event.reason === "string") {
      message = event.reason;
    } else if (typeof event.reason === "object") {
      message = event.reason.message;
    }
    let reason = event.reason;
    if (typeof reason === "object") {
      if (reason.stack) {
        var matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/);
        if (matchResult) {
          file = matchResult[1];
          line = matchResult[2];
          column = matchResult[3];
        }
        stack = getLines(reason.stack);
      }
    }
    window.$ajax({
      KEY: KEY,
      //未捕获的promise错误
      kind: "stability", //稳定性指标
      type: "error", //jsError
      errorType: "promiseError", //unhandledrejection
      message: message || '发生异常，但未被捕获', //标签名
      filename: file,
      position: line + ":" + column, //行列
      stack,
      selector: lastEvent
        ? getSelector(lastEvent.path || lastEvent.target)
        : "",
    });

  }, true)



  // 行为信息收集
  window.behaviour = {
    events: [],
    network: [],
    browser: [],
    console: []
  }
  // 事件行为监控
  const eventList = ["click", "touchstart"]
  eventList.forEach(
    (eventType) => {
      window.addEventListener(
        eventType,
        (event) => {
          // 记录
          behaviour.events.push(event)
        },
        {
          capture: true, // 是在捕获阶段还是冒泡阶段执行
          passive: true, // 默认不阻止默认事件
        }
      );
    }
  );
  // 网络行为监控
  networkListener()

  window.addEventListener('fetchReadystatechange', (e) => {
    behaviour.network.push(e)
  });
  // 监听页面的ajax
  window.addEventListener("ajaxReadyStateChange",function(e) {
    let xhr = e.detail;
    if(xhr.readyState == 4 && xhr.status == 200){
      // xhr.getAllResponseHeaders()  响应头信息
      // xhr.requestHeader            请求头信息
      // xhr.responseURL              请求的地址
      // xhr.responseText             响应内容
      // xhr.orignUrl                 请求的原始参数地址
      // xhr.body                     post参数，（get参数在url上面）
      behaviour.network.push(xhr)
    }
  });
  
  // 浏览器行为监控
  window.onpopstate = (to, from) => {
    // 收集信息
    behaviour.browser.push({
      timestamp: Date.now(),
      ...to,
      ...from
    })
  }
  // 控制台行为监控
  ;(() => {
    const typeList = ['log', 'info', 'error', 'warn']
    typeList.forEach(key => {
      console['_new'+key] = console[key]
      console[key] = (msg) => {
        console['_new'+key](msg)
        // 信息收集
        behaviour.console.push({
          timestamp: Date.now(),
          type: key,
          message: msg
        })
      }
    })
  })()
}









